<?php

namespace forfun\filter;

use forfun\constant\ErrorCode;
use forfun\exception\ForFunException;
use forfun\service\ApiTokenService;
use restphp\driver\RestDBRedis;
use restphp\http\request\RestHttpRequestIP;
use restphp\http\RestHttpRequest;
use restphp\http\RestHttpStatus;
use restphp\utils\RestHttpRequestUtils;
use restphp\utils\RestStringUtils;

class AuthFilter {
    private static $_headerKey = 'authorization';

    /**
     * @var string ForFun token={access_token};nonce={nonce};sign={sign}.
     */
    private static $_authorization = '';

    /**
     * 验证权限.
     * @return void
     * @throws ForFunException
     */
    public static function check() {
        self::$_authorization = RestHttpRequest::getHttpHeader(self::$_headerKey);
        if (RestStringUtils::isBlank(self::$_authorization)) {
            throw new ForFunException(ErrorCode::AUTH_ACCESS_DENIED, RestHttpStatus::Unauthorized);
        }

        // 获取认证类型
        if (!RestStringUtils::startWith(self::$_authorization, 'ForFun ')) {
            throw new ForFunException(ErrorCode::AUTH_TYPE_NOT_SUPPORT, RestHttpStatus::Unauthorized);
        }

        // 获取token值
        $token = substr(self::$_authorization, 7);
        if (RestStringUtils::isBlank($token)) {
            throw new ForFunException(ErrorCode::AUTH_BAD_TOKEN, RestHttpStatus::Unauthorized);
        }

        // 参数获取
        $accessToken = self::_match('/token=[a-zA-Z0-9]*;/', $token, 'token=', ';');
        if (RestStringUtils::isBlank($accessToken)) {
            throw new ForFunException(ErrorCode::AUTH_BAD_TOKEN, RestHttpStatus::Unauthorized);
        }
        $nonce = self::_match('/nonce=[a-zA-Z0-9]*;/', $token, 'nonce=', ';');
        if (RestStringUtils::isBlank($nonce)) {
            throw new ForFunException(ErrorCode::AUTH_BAD_TOKEN, RestHttpStatus::Unauthorized);
        }
        $sign = self::_match('/sign=[a-zA-Z0-9]*;/', $token, 'sign=', ';');
        if (RestStringUtils::isBlank($sign)) {
            throw new ForFunException(ErrorCode::AUTH_BAD_TOKEN, RestHttpStatus::Unauthorized);
        }

        //提取nonce里的时间和随机字符串
        if (mb_strlen($nonce) != 20) {
            throw new ForFunException(ErrorCode::AUTH_BAD_TOKEN, RestHttpStatus::Unauthorized);
        }
        $nonceTime = substr($nonce, 0, 14);
        $time = strtotime($nonceTime);
        if (null == $time) {
            throw new ForFunException(ErrorCode::AUTH_BAD_TOKEN, RestHttpStatus::Unauthorized);
        }
        if (time() - $time > 5 * 60) {
            throw new ForFunException(ErrorCode::AUTH_BAD_TOKEN, RestHttpStatus::Unauthorized);
        }
        $nonceRandom = substr($nonce, 14);
        // 重放检查
        $ip = RestHttpRequestIP::getAgentIp();
        $key = "ip:${ip}:${nonceRandom}";
        $redis = new RestDBRedis('API_TOKEN');
        if ($redis->exists($key)) {
            throw new ForFunException(ErrorCode::AUTH_BAD_TOKEN, RestHttpStatus::Unauthorized);
        }
        $redis->set($key, 1, 60);


        // 签名检查
        // 获取token
        $tokenBean = ApiTokenService::getByAccessToken($accessToken);
        if (null == $tokenBean) {
            throw new ForFunException(ErrorCode::AUTH_BAD_TOKEN, RestHttpStatus::Unauthorized);
        }
        if ($tokenBean->getState() != 1) {
            throw new ForFunException(ErrorCode::AUTH_TOKEN_EXPIRED, RestHttpStatus::Unauthorized);
        }

        // 签名内容
        $uri = RestHttpRequest::getUri();
        $method = RestHttpRequest::getHttpMethod();
        $strBody = self::_getBodyString();
        $accessKey = $tokenBean->getAccessKey();
        $signContent = "${uri}\n${method}\n${strBody}\n${nonce}\n${accessKey}";
        $signServer = strtoupper(hash('sha256', $signContent));
        if ($sign != $signServer) {
            //throw new ForFunException(ErrorCode::AUTH_TOKEN_CERTIFICATE_FAIL, RestHttpStatus::Unauthorized);
            ForFunException::throwRuntimeException(ErrorCode::AUTH_TOKEN_CERTIFICATE_FAIL, arraY(
                //$signServer,
                //$signContent
                '',''
            ));
        }
    }

    /**
     * 获取body参数.
     * @return string
     * @throws \restphp\exception\RestException
     */
    private static function _getBodyString() {
        $arrBody = RestHttpRequest::getBody();
        if (null == $arrBody || empty($arrBody)) {
            return '';
        }
        ksort($arrBody);

        $strBody = '';
        foreach ($arrBody as $key => $value) {
            $val = (is_array($value) || is_object($value)) ? json_encode($value, true) : $value;
            $strBody .= ($strBody == '' ? '' : '&') . "${key}=${val}";
        }
        return $strBody;
    }

    /**
     * 正则匹配.
     * @param $regex
     * @param $str
     * @param $cleanStart
     * @param $cleanEnd
     * @return string|null
     */
    private static function _match($regex, $str, $cleanStart = '', $cleanEnd = '') {
        $arrMatch = array();
        if (preg_match($regex, $str, $arrMatch)) {
            $strMatch = $arrMatch[0];
            return mb_substr($strMatch, mb_strlen($cleanStart), mb_strlen($strMatch) - mb_strlen($cleanStart) - mb_strlen($cleanEnd));
        }
        return null;
    }
}